import service
import trinity
import sys
import blue
import geo2
from math import sin, cos, tan, pi, sqrt, log10
GETPHOTO_ANGLE = (-0.3, 0.3, 0.0)
GETPHOTO_VIEW_UP = (0.0, 1.0, 0.0)
GETPHOTO_FOV = 1.0
GETPHOTO_PROJECTION = (GETPHOTO_FOV,
 1.0,
 1.0,
 500000.0)

class CorePhoto(service.Service):
    __guid__ = 'svc.photo'
    __update_on_reload__ = 0

    def _GetViewMatrixFromAngle(self, cameraAngle, lookAt, cameraDistance):
        view_at = geo2.Vector(lookAt[0], lookAt[1], lookAt[2])
        view_eye = geo2.Vector(0.0, 0.0, -1.0)
        angleTransform = geo2.MatrixRotationYawPitchRoll(cameraAngle[0], cameraAngle[1], cameraAngle[2])
        view_eye = geo2.Vec3TransformCoord(view_eye, angleTransform)
        view_eye = geo2.Vector(*view_eye)
        view_eye = ((view_eye * cameraDistance) + view_at)
        return (view_eye,
         view_at,
         geo2.Vector(*GETPHOTO_VIEW_UP))



    def _SphericalFit(self, radius, fov = GETPHOTO_FOV, fudge = 1.15):
        alpha = (fov / 2.0)
        return ((radius * (1 / tan(alpha))) * fudge)



    def TakeSnapShotUsingBoundingSphere(self, scene, size, boundingSphereRadius, boundingSphereCenter = None, cameraAngle = None, distanceOverride = None, transparentBackground = False, bgColor = None):
        self.LogInfo('TakeSnapShotUsingBoundingSphere')
        (view, projection,) = self.GetViewAndProjectionUsingBoundingSphere(boundingSphereRadius, boundingSphereCenter, cameraAngle, distanceOverride)
        return self.TakeSnapShot_Render(scene, size, view, projection, transparentBackground=transparentBackground, bgColor=bgColor)



    def TakeSnapShotUsingBoundingBox(self, scene, size, boundingBoxMin = None, boundingBoxMax = None, cameraAngle = None, transparentBackground = False, bgColor = None):
        (view, projection,) = self.GetViewAndProjectionUsingBoundingBox(boundingBoxMin, boundingBoxMax, scene, cameraAngle)
        return self.TakeSnapShot_Render(scene, size, view, projection, transparentBackground=transparentBackground, bgColor=bgColor)



    def TakeSnapShotUsingMeshGeometry(self, scene, size, geometry, geometryMeshIdx = 0, boundingSphereRadius = None, boundingSphereCenter = None, boundingBoxMin = None, boundingBoxMax = None, cameraAngle = None, transparentBackground = False, bgColor = None):
        (view, projection,) = self.GetViewAndProjectionUsingMeshGeometry(geometry, geometryMeshIdx, scene, boundingSphereRadius, boundingSphereCenter, boundingBoxMin, boundingBoxMax, cameraAngle)
        return self.TakeSnapShot_Render(scene, size, view, projection, transparentBackground=transparentBackground, bgColor=bgColor)



    def GetViewAndProjectionUsingBoundingSphere(self, boundingSphereRadius, boundingSphereCenter = None, cameraAngle = None, distanceOverride = None):
        cameraAngle = (cameraAngle or GETPHOTO_ANGLE)
        boundingSphereCenter = (boundingSphereCenter or (0.0, 0.0, 0.0))
        dist = (distanceOverride if distanceOverride else self._SphericalFit(boundingSphereRadius))
        projection = trinity.TriProjection()
        projection.PerspectiveFov(*GETPHOTO_PROJECTION)
        view = trinity.TriView()
        view.SetLookAtPosition(*self._GetViewMatrixFromAngle(cameraAngle, boundingSphereCenter, dist))
        return (view, projection)



    def GetViewAndProjectionUsingBoundingBox(self, boundingBoxMin = None, boundingBoxMax = None, scene = None, cameraAngle = None):

        def CalculateProjectedBoundingBox(combinedTransform):
            edges = []
            edges.append(geo2.Vector(boundingBoxMin.x, boundingBoxMin.y, boundingBoxMin.z))
            edges.append(geo2.Vector(boundingBoxMin.x, boundingBoxMin.y, boundingBoxMax.z))
            edges.append(geo2.Vector(boundingBoxMin.x, boundingBoxMax.y, boundingBoxMin.z))
            edges.append(geo2.Vector(boundingBoxMin.x, boundingBoxMax.y, boundingBoxMax.z))
            edges.append(geo2.Vector(boundingBoxMax.x, boundingBoxMin.y, boundingBoxMin.z))
            edges.append(geo2.Vector(boundingBoxMax.x, boundingBoxMin.y, boundingBoxMax.z))
            edges.append(geo2.Vector(boundingBoxMax.x, boundingBoxMax.y, boundingBoxMin.z))
            edges.append(geo2.Vector(boundingBoxMax.x, boundingBoxMax.y, boundingBoxMax.z))
            for (i, edge,) in enumerate(edges):
                edge = geo2.Vector(*geo2.Vec3TransformCoord(edge, combinedTransform))
                if (i == 0):
                    safeMin = geo2.Vector(*edge)
                    safeMax = geo2.Vector(*edge)
                else:
                    safeMin.x = min(safeMin.x, edge[0])
                safeMin.y = min(safeMin.y, edge[1])
                safeMin.z = min(safeMin.z, edge[2])
                safeMax.x = max(safeMax.x, edge[0])
                safeMax.y = max(safeMax.y, edge[1])
                safeMax.z = max(safeMax.z, edge[2])

            return (safeMin, safeMax)


        return self._GetViewAndProjectionUsingProjectedBoundingBox(CalculateProjectedBoundingBox, scene, boundingBoxMin=boundingBoxMin, boundingBoxMax=boundingBoxMax, cameraAngle=cameraAngle, boundingSphereRadius=None, boundingSphereCenter=None)



    def GetViewAndProjectionUsingMeshGeometry(self, geometry, geometryMeshIdx = 0, scene = None, boundingSphereRadius = None, boundingSphereCenter = None, boundingBoxMin = None, boundingBoxMax = None, cameraAngle = None):

        def CalculateProjectedBoundingBox(combinedTransform):
            return geometry.CalculateBoundingBoxFromTransform(geometryMeshIdx, combinedTransform)


        return self._GetViewAndProjectionUsingProjectedBoundingBox(CalculateProjectedBoundingBox, scene, boundingSphereRadius=boundingSphereRadius, boundingSphereCenter=boundingSphereCenter, boundingBoxMin=boundingBoxMin, boundingBoxMax=boundingBoxMax, cameraAngle=cameraAngle)



    def _GetViewAndProjectionUsingProjectedBoundingBox(self, CalculateProjectedBoundingBox, scene = None, boundingSphereRadius = None, boundingSphereCenter = None, boundingBoxMin = None, boundingBoxMax = None, cameraAngle = None):
        self.LogInfo('TakeSnapShotUsingBoundingBox')
        cameraAngle = (cameraAngle or GETPHOTO_ANGLE)
        if boundingSphereRadius:
            radius = boundingSphereRadius
            center = (boundingSphereCenter if boundingSphereCenter else (0.0, 0.0, 0.0))
        elif (boundingBoxMin and boundingBoxMax):
            boundingBoxMin = geo2.Vector(boundingBoxMin.x, boundingBoxMin.y, boundingBoxMin.z)
            boundingBoxMax = geo2.Vector(boundingBoxMax.x, boundingBoxMax.y, boundingBoxMax.z)
            center = ((boundingBoxMin + boundingBoxMax) / 2.0)
            radius = geo2.Vec3Length((boundingBoxMax - boundingBoxMin))
        else:
            raise RuntimeError('Can not do a rough fit without either a bounding sphere or bounding box.')
        dist = self._SphericalFit(radius)
        viewEyeAtUp = self._GetViewMatrixFromAngle(cameraAngle, center, dist)
        projTransform = geo2.MatrixPerspectiveFovLH(*GETPHOTO_PROJECTION)
        viewTransform = geo2.MatrixLookAtLH(*viewEyeAtUp)
        combinedTransform = viewTransform
        combinedTransform = geo2.MatrixMultiply(combinedTransform, projTransform)
        (safeMin, safeMax,) = CalculateProjectedBoundingBox(combinedTransform)
        deltaX = (safeMax[0] - safeMin[0])
        deltaY = (safeMax[1] - safeMin[1])
        scalingFactor = (0.9 * (2.0 / max(deltaX, deltaY)))
        try:
            if (scene.backgroundEffect is not None):
                params = scene.backgroundEffect.Find('trinity.TriFloatParameter')
                for param in params:
                    if (param.name == 'ProjectionScaling'):
                        param.value = scalingFactor

        except AttributeError:
            pass
        offsetX = (((-1 * scalingFactor) * (safeMin[0] + safeMax[0])) / 2.0)
        offsetY = (((-1 * scalingFactor) * (safeMin[1] + safeMax[1])) / 2.0)
        scale = ((1.0 / tan((GETPHOTO_FOV / 2.0))) * scalingFactor)
        zn = 1.0
        zf = (dist + (radius * 2))
        t = ((zn * (1 - offsetY)) / scale)
        b = ((-t * (1 + offsetY)) / (1 - offsetY))
        r = ((zn * (1 - offsetX)) / scale)
        l = ((-r * (1 + offsetX)) / (1 - offsetX))
        projection = trinity.TriProjection()
        projection.PerspectiveOffCenter(l, r, b, t, zn, zf)
        view = trinity.TriView()
        view.SetLookAtPosition(*viewEyeAtUp)
        return (view, projection)



    def TakeSnapShot_Render(self, scene, size, view, projection, transparentBackground = False, bgColor = None):
        self.LogInfo('TakeSnapShot_Render')
        (msType, msQuality,) = (0, 0)
        if transparentBackground:
            bbFormat = trinity.TRIFMT_A8R8G8B8
            clearColor = (bgColor or (0.0, 0.0, 0.0, 0.0))
        else:
            bbFormat = trinity.TRIFMT_X8R8G8B8
            clearColor = (bgColor or (0.0, 0.0, 0.0, 1.0))
        dsFormat = trinity.TRIFMT_D24S8
        if (scene.__bluetype__ == 'trinity.EveSpaceScene'):
            try:
                (msType, msQuality,) = sm.GetService('device').GetMultiSampleQualityOptions()[-1][1]
            except:
                (msType, msQuality,) = (0, 0)
        target = trinity.TriSurfaceManaged(trinity.device.CreateRenderTarget, size, size, bbFormat, msType, msQuality, 0)
        stencil = trinity.TriSurfaceManaged(trinity.device.CreateDepthStencilSurface, size, size, dsFormat, msType, msQuality, 0)
        surface = trinity.TriSurfaceManaged(trinity.device.CreateOffscreenPlainSurface, size, size, bbFormat, trinity.TRIPOOL_SYSTEMMEM)
        self.LogInfo('TakeSnapShot_Render_After_Resource_Creation')
        renderJob = trinity.CreateRenderJob('TakeSnapShot')
        renderJob.SetRenderTarget(target)
        renderJob.SetProjection(projection)
        renderJob.SetView(view)
        renderJob.SetDepthStencil(stencil)
        renderJob.Clear(clearColor, 1.0)
        renderJob.Update(scene)
        renderJob.RenderScene(scene)
        trinity.WaitForResourceLoads()
        renderJob.ScheduleOnce()
        renderJob.WaitForFinish()
        if (msType != 0):
            bounceSurface = trinity.TriSurfaceManaged(trinity.device.CreateRenderTarget, size, size, bbFormat, 0, 0, 1)
            trinity.device.StretchRect(target, None, bounceSurface, None)
            target = bounceSurface
        trinity.device.GetRenderTargetData(target, surface)
        return surface



    def _SaveSurfaceToFile(self, surface, outputPath, format = trinity.TRIIFF_DDS):
        filename = (blue.os.cachepath + outputPath)
        cachename = ('cache:/' + outputPath)
        self.LogInfo('Saving surface ', cachename, ' to file ', filename)
        surface.SaveSurfaceToFile(filename, format)
        return cachename




